use std::collections::{HashSet, hash_map};

use libp2p::request_response::ResponseChannel;
use libp2p::{Multiaddr, PeerId, gossipsub::MessageId, multiaddr::Protocol};
use tokio::sync::oneshot;
use tracing::{error, info};

use crate::ProvidingContentId;
use crate::network_api::P2PCommandOfApp;
use crate::network_core::P2pNetWorkError;
use crate::network_core::network_core_impl::combined_behaviour::ContentResponse;
use crate::network_core::network_core_impl::network_manager::NetworkManager;
use crate::network_core::network_core_inner_api::CommandOfNetWorkCore;

impl NetworkManager {
    pub(super) async fn handle_command(&mut self, command: CommandOfNetWorkCore) {
        info!("++++++++++++Handling network command {:?}.", command);
        match command {
            // UserCommand::StartListening {
            //     addr,
            //     cmd_ack_sender: sender,
            // } => {
            //     let _ = match self.swarm.listen_on(addr) {
            //         Ok(listener) => {
            //             self.local_listeners.push(listener);
            //             println!("[{}] Node started", self.swarm.local_peer_id());
            //             sender.send(Ok(()))
            //         }
            //         Err(e) => sender.send(Err(P2pNetWorkError::SwarmTransportError(e))),
            //     };
            // }
            CommandOfNetWorkCore::CleanupSwarm { cmd_ack_sender } => {
                self.do_cleanup_swarm(cmd_ack_sender)
            }
        }
    }

    pub(super) async fn handle_command_of_app(&mut self, command: P2PCommandOfApp) {
        match command {
            P2PCommandOfApp::Broadcast {
                topic,
                data,
                cmd_ack_sender: sender,
            } => self.do_gossipsub_publish(topic, data, sender),

            P2PCommandOfApp::RegisterTopic { topic } => self.do_register_topic(&topic),

            P2PCommandOfApp::StartSync {
                peer: _,
                chunk_ids: _,
            } => {
                // self.swarm
                //     .behaviour_mut()
                //     .request_response
                //     .send_request(&peer, (topic, data));
            }
            P2PCommandOfApp::Dial {
                peer_id,
                peer_addr,
                cmd_ack_sender: sender,
            } => self.do_dail(peer_id, peer_addr, sender),

            P2PCommandOfApp::StartProviding {
                providing_content_id,
                cmd_ack_sender,
            } => self.do_start_providing(&providing_content_id, cmd_ack_sender),

            P2PCommandOfApp::GetProviders {
                providing_content_id,
                cmd_ack_sender,
            } => self.do_get_providers(&providing_content_id, cmd_ack_sender),

            P2PCommandOfApp::RequestContent {
                providing_content_id,
                peer,
                response_sender,
            } => self.do_request_file(&providing_content_id, peer, response_sender),

            P2PCommandOfApp::RespondContent {
                content: file_data,
                channel,
            } => self.do_respond_file_content(file_data, channel),

            P2PCommandOfApp::GetLocalPeerId { cmd_ack_sender } => {
                self.do_get_local_peer_id(cmd_ack_sender)
            }
        }
    }

    fn do_gossipsub_publish(
        &mut self,
        topic: String,
        data: Vec<u8>,
        sender: oneshot::Sender<Result<MessageId, crate::BcP2pError>>,
    ) {
        match self.swarm.behaviour_mut().gossipsub_publish(&topic, data) {
            Ok(message_id) => {
                info!(
                    "[{}] Published message {:?} to topic {:?}",
                    self.local_peer_id, message_id, topic
                );
                sender.send(Ok(message_id)).unwrap();
            }
            Err(e) => {
                error!("Failed to publish message to topic({}): {:?}", topic, e);

                sender.send(Err(e.into())).unwrap();
            }
        }
    }

    fn do_cleanup_swarm(&mut self, cmd_ack_sender: oneshot::Sender<Result<(), P2pNetWorkError>>) {
        while let Some(listerner) = self.local_listeners.pop() {
            self.swarm.remove_listener(listerner);
        }

        // let mut peer_ids: Vec<PeerId> = Vec::new();
        // for peer_id in self.swarm.connected_peers() {
        //     peer_ids.push(*peer_id);
        // }

        // for peer_id in peer_ids {
        //     match self.swarm.disconnect_peer_id(peer_id) {
        //         Ok(_) => {}
        //         Err(_) => {
        //             error!("Peer {peer_id} had disconnected.");
        //         }
        //     }
        // }

        let _ = cmd_ack_sender.send(Ok(()));

        self.swarm.behaviour_mut().cleanup();
    }

    fn do_dail(
        &mut self,
        peer_id: PeerId,
        peer_addr: Multiaddr,
        sender: oneshot::Sender<Result<(), P2pNetWorkError>>,
    ) {
        if let hash_map::Entry::Vacant(e) = self.pending_dial.entry(peer_id) {
            self.swarm
                .behaviour_mut()
                .add_address_kademlia(&peer_id, peer_addr.clone());

            match self.swarm.dial(peer_addr.with(Protocol::P2p(peer_id))) {
                Ok(()) => {
                    e.insert(sender);
                }
                Err(e) => {
                    let _ = sender.send(Err(P2pNetWorkError::SwarmDialError(e)));
                }
            }
        } else {
            todo!("Already dialing peer.");
        }
    }

    fn do_start_providing(
        &mut self,
        providing_content_id: &ProvidingContentId,
        cmd_ack_sender: oneshot::Sender<()>,
    ) {
        self.swarm.behaviour_mut().start_providing_kademlia(
            providing_content_id,
            cmd_ack_sender,
            &mut self.kad_metadata,
        );
    }

    fn do_get_providers(
        &mut self,
        providing_content_id: &ProvidingContentId,
        cmd_ack_sender: oneshot::Sender<HashSet<PeerId>>,
    ) {
        self.swarm.behaviour_mut().get_providers_kademlia(
            providing_content_id,
            cmd_ack_sender,
            &mut self.kad_metadata,
        );
    }

    fn do_request_file(
        &mut self,
        providing_content_id: &ProvidingContentId,
        peer: PeerId,
        response_sender: oneshot::Sender<Result<Vec<u8>, P2pNetWorkError>>,
    ) {
        self.swarm.behaviour_mut().send_request_file(
            providing_content_id,
            peer,
            response_sender,
            &mut self.request_response_metadata,
        );
    }

    fn do_respond_file_content(
        &mut self,
        file_data: Result<Vec<u8>, String>,
        channel: ResponseChannel<ContentResponse>,
    ) {
        info!("RespondFile:({:?})", file_data);
        self.swarm
            .behaviour_mut()
            .send_response_file_to_network(file_data, channel);
    }

    fn do_get_local_peer_id(&mut self, cmd_ack_sender: oneshot::Sender<PeerId>) {
        match cmd_ack_sender.send(self.local_peer_id) {
            Ok(_) => {}
            Err(peer_id) => {
                error!("Failed to send local peer id({peer_id}).");
            }
        }
    }

    fn do_register_topic(&mut self, topic: &str) {
        match self.swarm.behaviour_mut().gossipsub_subscribe(topic) {
            Ok(_) => (),
            Err(e) => {
                error!("Failed to register topic({}): {:?}", topic, e);
            }
        }
    }
}
